﻿/*  ----------------------------------------------------------------------------
 *  DES
 *  ----------------------------------------------------------------------------
 *  File:       ServiceQueueBalancer.cs
 *  Author:     nitinkoshy@gmail.com
 *  ----------------------------------------------------------------------------
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Configuration;
using System.ServiceModel;
using System.Diagnostics;

namespace DES.Server.Service
{
    //visits the ServerService queue once in a while
    //and if load over threshhold, moves the task to a different server.
    public class ServiceQueueBalancer
    {
        private Thread _visitorThread = new Thread(CheckQueue);
        private static bool _stopped = false;
        private static int _threadSleepTime;

        public ServiceQueueBalancer()
        {
            _threadSleepTime = Convert.ToInt16(ConfigurationManager.AppSettings["balancerthreadsleeptime"]);
        }

        public static void CheckQueue(object data)
        {
            DESServerService serverService = (DESServerService ) data;
            while (!_stopped)
            {
                Thread.Sleep(_threadSleepTime);
                if (serverService.CurrentLoad > serverService.MaxAllowedLoad)
                {
                    //the server is already on a load, hence move the later tasks
                    //to other servers
                    while (serverService.TaskQueue.Count > serverService.MaxAllowedLoad)
                    {
                        Trace.WriteLine(String.Format("ServiceQueueBalancer Message : Current task queue load over max allowed"));
                        ServerTask serverTask;
                        lock (serverService.TaskQueue)
                        {
                            serverTask = serverService.TaskQueue.Dequeue();
                        }


                        int leastLoadedServerLoad = 0;
                        DESServerInfo leastLoadedServer = LocateLeastLoadedServer(serverService.ConnectedServers, out leastLoadedServerLoad);

                        if (leastLoadedServer != null)
                          Trace.WriteLine(String.Format("ServiceQueueBalancer Message : Located least loaded server : {0}",leastLoadedServer.Id));
                        //ignore if the least loaded server is already more loaded than me
                        if (leastLoadedServerLoad >= serverService.CurrentLoad)
                        {
                            leastLoadedServer = null;
                            Trace.WriteLine(String.Format("ServiceQueueBalancer Message : least loaded server load > current load. Not Balancing"));
                        }

                        if (leastLoadedServer == null)//no servers located
                        {
                            lock (serverService.TaskQueue)
                            {
                                serverService.TaskQueue.Enqueue(serverTask);//put the task back
                            }
                            Trace.WriteLine(String.Format("ServiceQueueBalancer Message : No possible server located to de-load"));
                            break; //nothing more to check.
                        }

                        ChannelFactory<IDESServerService> desServerServiceFactory = new ChannelFactory<IDESServerService>(
                            new NetTcpBinding(),
                            new EndpointAddress(leastLoadedServer.EndPoint));
                        IDESServerService desServerService = desServerServiceFactory.CreateChannel();

                       try
                        {
                            Trace.WriteLine(String.Format("ServiceQueueBalancer Message : Unloading task to server {0}", leastLoadedServer.Id));
                            desServerService.QueueTask(serverTask, serverService.ServerInfo);
                            desServerServiceFactory.Close();
                        }
                        catch (EndpointNotFoundException exception)//this server is not connected
                        {
                            Trace.WriteLine(String.Format("ServiceQueueBalancer Message : Could not Unload task to server {0}", leastLoadedServer.Id));
                            lock (serverService.TaskQueue)
                            {
                                serverService.TaskQueue.Enqueue(serverTask);//put the task back
                            }
                            continue;
                        }

                    }
                }
            }
        }

        private static DESServerInfo LocateLeastLoadedServer(List<DESServerInfo> connectedServers, out int leastLoadedServerLoad)
        {
            DESServerInfo serverInfo = null;
            int serverLoad, minLoad = -1;

            foreach (DESServerInfo server in connectedServers)
            {
                ChannelFactory<IDESServerService> desServerServiceFactory = new ChannelFactory<IDESServerService>(
                    new NetTcpBinding(),
                    new EndpointAddress(server.EndPoint));

                IDESServerService desServerService = desServerServiceFactory.CreateChannel();
                try
                {
                    serverLoad = desServerService.GetServerLoad();
                    desServerServiceFactory.Close();
                }
                catch (EndpointNotFoundException exception)
                {
                    continue; //this server is not connected. try the next.
                }

                if (minLoad == -1)
                {
                    minLoad = serverLoad;
                    serverInfo = server;
                }
                else if (serverLoad < minLoad)
                {
                    minLoad = serverLoad;
                    serverInfo = server;
                }
                
            }
            leastLoadedServerLoad = minLoad;
            return serverInfo;
        }

        public void StartMonitoring(DESServerService serverService)
        {
            _visitorThread.Start(serverService);
            Trace.WriteLine(String.Format("ServiceQueueBalancer thread started with sleep time {0}", _threadSleepTime.ToString()));
        }

        public void StopMonitoring()
        {
            _stopped = true;
        }
    }
}
